package com.lagou.rpc.consumer.proxy;

import com.alibaba.fastjson.JSON;
import com.lagou.rpc.common.RpcRequest;
import com.lagou.rpc.common.RpcResponse;
import com.lagou.rpc.common.ServiceInfo;
import com.lagou.rpc.consumer.client.RpcClient;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 客户端代理类-创建代理对象
 * 1. 封装RpcRequest对象
 * 2. 创建RpcClient对象
 * 3. 发送消息
 * 4. 返回结果
 */
public class RpcClientProxy {
//    private static int count = 0;

    //缓存本地请求数据
    //当前本地有请求时，将请求相关信息保存在此map中
    //该map中的数据每隔5秒上传到zookeeper中
    public static Map<String,ServiceInfo> localServiceInfoMap = new HashMap<>();

    //改map数据由zookeeper监听器维护，即与zookeeper中数据同步
    //请求过程中从此map中依照负载均衡规则获取响应的service信息
    public static Map<String,ServiceInfo> remoteServiceInfoMap = new HashMap<>();

    //声明在方法内，下面的内部类无法使用局部变量，没办法，声明到这里了
    private static String name = null;
    private static ServiceInfo serviceInfo = null;

    public static Object createProxy(Class serviceClass){

        //根据负载均衡规则，获取响应的name和serviceInfo对象
        boolean hasEmptyService = false;
        //对remoteServiceInfoMap进行遍历
        for (Map.Entry<String, ServiceInfo> entry : remoteServiceInfoMap.entrySet()) {
            name = entry.getKey();
            serviceInfo = entry.getValue();
            // 如果map中有请求时间和响应时间都为0的对象，停止变量，使用这个对象进行RPC调用
            if(serviceInfo.getRequestTimestamp() == 0 && serviceInfo.getResponseDuration() == 0){
                hasEmptyService = true;
                break;
            }
        }
        //如果没有空闲的服务对象
        if(!hasEmptyService){
            //重新遍历map，选择时间最短的
            long responseDuration = Long.MAX_VALUE;
            for (Map.Entry<String, ServiceInfo> entry : remoteServiceInfoMap.entrySet()) {
                ServiceInfo tempServiceInfo = entry.getValue();
                if(tempServiceInfo.getResponseDuration() < responseDuration){
                    responseDuration = tempServiceInfo.getResponseDuration();
                    name = entry.getKey();
                    serviceInfo = entry.getValue();
                }
            }
        }
        if(name == null){
            System.out.println("Error：未发现可用服务");
            return null;
        }
        /*-----------------负载均衡选择服务结束-------------------*/
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        Class[] classes =  {serviceClass};
        return Proxy.newProxyInstance(contextClassLoader, classes, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //1. 创建并封装RpcRequest对象
                RpcRequest rpcRequest = new RpcRequest();
                rpcRequest.setRequestId(UUID.randomUUID().toString());
                rpcRequest.setMethodName(method.getName());
                rpcRequest.setClassName(serviceClass.getName());
                rpcRequest.setParameterTypes(method.getParameterTypes());
                rpcRequest.setParameters(args);

                //2. 创建RpcClent
                RpcClient rpcClient = new RpcClient(serviceInfo.getIp(), serviceInfo.getPort());
                try {
                    //创建一个ServiceInfo对象，保存请求时间信息
                    ServiceInfo localServiceInfo = new ServiceInfo(serviceInfo.getIp(), serviceInfo.getPort());
                    //获取当前请求的系统时间
                    long l1 = System.currentTimeMillis();
                    localServiceInfo.setRequestTimestamp(l1);
                    //3. 调用send方法发送消息
                    Object resoneseMsg = rpcClient.send(JSON.toJSONString(rpcRequest));
                    //获取当前系统时间，计算请求时间
                    long l2 = System.currentTimeMillis();
                    localServiceInfo.setResponseDuration(l2-l1);
                    //将localServiceInfo保存在本地，等待下一次请求覆盖或提交到zookeeper
                    localServiceInfoMap.put(name,localServiceInfo);

                    RpcResponse rpcResponse = (RpcResponse)JSON.parseObject(resoneseMsg.toString(), RpcResponse.class);

                    if(rpcResponse.getError() != null){
                        throw new RuntimeException(rpcResponse.getError());
                    }

                    Object result = rpcResponse.getResult();

                    return JSON.parseObject(result.toString(),method.getReturnType());

                }catch (Exception e){
                    throw e;
                }finally {
                    rpcClient.close();
                }
            }
        });
    }
}
